/**
 * 猛虎出笼 MHCL 众赢投顾 主图指标
 */

import {movingAverage as _average} from './MA'
import * as zytgTools from './ZYTG_UTIL'

const MHCL_VOLUME_INDEX = 2    //成交量倍数系数
const MHCL_LOW_SAFT_LINE = 0.95   //最低价偏离系数
const MHCL_SA_DISTINCE = 3       //SA距离天数
const MHCL_MIN_PREPARE_DATA = 150   //最小数据准备天数

let initParam = {
  name: 'MHCL',
  type: 'polyline',
  dataAppendCount: 240,  // 默认用
}

/**
 * Caculator 数据处理
 *
 */
/**
 * Caculator 数据处理
 * @param  {array} rawData
 * @return {array} 一维数组
 *
 *
 * 数据结构说明
[
  {
      "BX": 'B点x坐标',
      "BTime": 'B点时间',
      "SAX": 'SA点x坐标',
      "SATime": 'SA点时间',
      "startX": 86,
      "endX": 90,
      "startTime": "2017-07-11",
      "endTime": "2017-07-16",
      "startY": 86,
      "endY": 90,
      "length": 4,
      "color": "blue",
      "format": "rect",
      "title": "T = 4"
  }
]
 */
function caculate (rawData, dataSliceCount) {
  if (!(rawData instanceof Array)) throw new Error('Data or caculateParam is not Array')
  let mathData = _caculator(rawData, dataSliceCount)
  return mathData
}

function _caculator (rawData, dataSliceCount) {
  let dataPoints = []
  let startDay  //H0和B的搜索起始点
  let firstH0   //符合条件的B点最左侧H0
  let B_Ready   //最终符合所有矩形展示规则的B点
  let leftFindingH0 //是否继续往左查找扩展h0
  let hasAnotherB   //是否左侧找到另一类B点
  let pendingH0             //等待使用的左侧H0

  //30日内最低价的最低值
  let minLow30Days = zytgTools.calcMaxMinOfDays(rawData, 'low', 30, zytgTools.MMD_GET_MIN)
  //10日内最低价的最低值
  let minLow10Days = zytgTools.calcMaxMinOfDays(rawData, 'low', 10, zytgTools.MMD_GET_MIN)
  //成交量120日均量
  let maArray = zytgTools.calcMAByFields(rawData, [
    { field: "volume", period: "120" },
    { field: "close", period: "60" }
  ])
  let cacheH0 = []
  let extra = {
    L30: minLow30Days,
    L10: minLow10Days,
    close60: maArray.close60,
    volume120: maArray.volume120,
    cache: {}, 
    getCache: function(cat, key, callback) {
      if (this.cache[cat] === undefined) this.cache[cat] = []
      let map = this.cache[cat]
      if (map[key] === undefined) {
        map[key] = callback()
      }
      return map[key]
    }
  }
  let AAs = calcAAs(rawData, extra)
  extra.AAs = AAs

  for (let i = dataSliceCount; i < rawData.length; i++) {
    let H0DD = rawData[i].low //H0到当前低点
    let lastSA                
    let top, bottom
    let Current_AA = extra.AAs[i]
    if (Current_AA < 0) {
      continue
    }
    //从上一个交易日开始查找
    startDay = i //H0和B的搜索起始点
    firstH0 = null //符合条件的B点最左侧H0
    B_Ready = false //最终符合所有矩形展示规则的B点
    leftFindingH0 = true //是否继续往左查找扩展h0
    hasAnotherB = false //是否左侧找到另一类B点
    pendingH0 = null             //等待使用的左侧H0

    while (startDay >= 0) {
      let searchDay = startDay - 1
      //{上一H0距今天数}
      let lastH0 = extra.getCache('h0', searchDay, function(){
          return zytgTools.findDays(rawData, searchDay, true, [
          function (raw, index, extra) {
              return checkH0(raw, index, extra)
          }
        ], extra)
      })
      if (lastH0 < 0) break

      let H0JL = searchDay - lastH0
      //{H0JL+1日内最高价的最高值}
      let H0GD = zytgTools.findMaxMinOfDays(rawData, searchDay, H0JL + 1, zytgTools.MMD_GET_MAX, 'high')

      //{定义B：收盘大于昨日H0GD值，并满足VOCS条件，同时SA到现在的距离>=3} 
      let H0_Ready = rawData[startDay].close > H0GD
      let hasB = H0_Ready && checkVOCS(rawData, startDay, getAA_JL(extra.AAs, startDay), extra) 
      if (hasB) {
        lastSA = getLastSA(rawData, startDay, extra)
        hasB = hasB && lastSA >= 0 && (startDay - lastSA >= MHCL_SA_DISTINCE)
      }
      // 继续往前找符合条件（H0_Ready + 高低点针对最终B的约束）的H0, 扩展矩形宽度
      if (startDay == i) {
        B_Ready = hasB
        firstH0 = pendingH0 = lastH0
      } else {
        //找出H0以来的首个B
        if (hasB) {
          if (dataPoints.length) {
            let old = dataPoints[dataPoints.length - 1]
            //是否是当前检查日的矩形
            let inOld = (startDay >= old.startX) && (startDay <= old.endX) || (old.BX == startDay)
            if (!inOld || startDay <= firstH0) {
              hasAnotherB = true
            }
          } else {
            hasAnotherB = true
          }
        }
        updateFindingStatus()
        if (B_Ready && leftFindingH0) {
          if (lastH0 < pendingH0) {
            //像左扩展的h0同时需要满足收盘大于昨日H0GD值,并且H0到这个B的最低点不小于H0的低点L*0.95，否则停止扩展
            if (rawData[i].close > H0GD && (H0DD >= rawData[lastH0].low * MHCL_LOW_SAFT_LINE)) {
              pendingH0 = lastH0
              //H0List.push(lastH0)
            } else {
              leftFindingH0 = false
            }
          }
        }

        //计算B点意外的最低价(暂定，用于辅助判断是否可以左边扩展h0)
        if (B_Ready) {
          bottom = bottom ? Math.min(bottom, rawData[startDay].low) : rawData[startDay].low
          H0DD = Math.min(bottom, H0DD)
        }
      }

      startDay --
      if (!B_Ready || (!leftFindingH0)) {
        break
      }
      if (startDay <= Current_AA) {
        break
      }
    }
    if (B_Ready) {
      updateFindingStatus()

      //并且H0到这个B的最低点不小于H0的低点L*0.95
      H0DD = zytgTools.findMaxMinOfDays(rawData, i - 1, i - firstH0, zytgTools.MMD_GET_MIN, 'low')
      bottom = H0DD
      H0DD = Math.min(H0DD, rawData[i].low)
      B_Ready = (H0DD >= rawData[firstH0].low * MHCL_LOW_SAFT_LINE)
      
      top = zytgTools.findMaxMinOfDays(rawData, i - 1, i - firstH0, zytgTools.MMD_GET_MAX, 'high')
    }
    if (B_Ready) {
      //在B对应的最左侧的H0 到B前一根K线之间画矩形（矩形上沿为H0到B前一根K线的最高点，下沿为H0到B前一根K线的最低点）
      //同时在矩形上方标出K线根数
      let box = {
            startX: firstH0,
            endX: i - 1,
            startTime: rawData[firstH0].time,
            endTime: rawData[i - 1].time,
            startY: bottom,
            endY: top,
            color: 'blue',
            format: 'rect',
            title: 'T = ' + (i - firstH0),
            BX: i,
            SAX: lastSA,
            BTime: rawData[i].time,
            SATime: rawData[lastSA].time
      }

      //如果B到对应的最左侧H0中间包含其他矩形，那么其他矩形不再显示，只显示最大的矩形
      if (dataPoints.length) {
        let lastBox = dataPoints[dataPoints.length - 1]
        if (lastBox.startX >= box.startX) {
          if (lastBox.startX > box.startX) {
            //对于没有更左侧h0可以对应的右侧B，不允许做包含
            if (lastBox.endX < box.endX) {
              dataPoints.splice(dataPoints.length - 1, 1, box)
            }
          }
        // } else if (lastBox.BX == box.startX) {
        //   //两个区间正好连接在一起，所以合并
        //   lastBox.endX = box.endX
        //   lastBox.title = 'T = ' + (box.BX - lastBox.startX)
        //   lastBox.endTime = box.endTime
        //   lastBox.endY = box.endY
        //   lastBox.BX = box.BX
        //   lastBox.BTime = box.BTime
        } else {
          dataPoints.push(box)
        }
      } else {
        dataPoints.push(box)
      }
    }
  }


  for (let j = 0; j < dataPoints.length; j++) {
    dataPoints[j].startX -= dataSliceCount
    dataPoints[j].endX -= dataSliceCount
    dataPoints[j].BX -= dataSliceCount
    dataPoints[j].SAX -= dataSliceCount
  }

  // console.log(JSON.stringify(dataPoints))
  return dataPoints

  function updateFindingStatus(){
    if (!B_Ready) return
    if (leftFindingH0) {
      //左侧h0的使用，需要和startDay同步，避免出现另一个B点，而导致此h0无效
      if (pendingH0 == startDay && pendingH0 < firstH0) {
        firstH0 = pendingH0
      }
    }
    if (hasAnotherB) {
      B_Ready = startDay <= firstH0
    }
    leftFindingH0 = !hasAnotherB
  }
}


function calcAAs(rawData, extra) {
  let result = []
  let AA
  for (let i = 0; i < rawData.length; i++) {
    //定义低点AA：最低价<1日前的30日内最低价的最低值并且最低价<10日后的10日内最低价的最低值
    let day = rawData[i]
    let l30 = extra.L30[i - 1] ? extra.L30[i - 1] : 0
    let l10 = extra.L10[i + 10] ? extra.L10[i + 10] : 0        
    if ((day.low < l30) && (day.low < l10)) {
      AA = i
    }
    result.push(AA ? AA : -1)
  }
  //定义AAJL：上次AA距今天数(含今天)
  //todo: 由于目前不是全部k线计算，原则上可能出现找不到，但实际是存在的情况
  return result 
}

function getAA_JL(AAs, i) {
  let days = AAs[i] < 0 ? -1 : i - AAs[i]
  //限制不要无限的往前找AA，避免缺乏足够的配套数据引发误判
  return (AAs[i] < initParam.dataAppendCount - MHCL_MIN_PREPARE_DATA) ? -1 : days
}

function getLastSA(rawData, i, extra) {
  return extra.getCache('sa', i, function(){
    return zytgTools.findDays(rawData, i, true, [
      function (raw, index, extra) {
          return checkSA(raw, index, extra)
      }
    ], extra)
  })
}

function checkH0(rawData, i, extra) {
    //定义AAJL：上次AA距今天数(含今天)
    let AA_JL = getAA_JL(extra.AAs, i)
    if (AA_JL < 0) return false

    //HO定义，{同时满足以下条件：未来三日的收盘均低于计算K线最高价；未来三日最低价均大于计算k线的最低价*0.95；满足vocs条件；sa到计算K线的距离大于0；aajl要大于sa到计算K线的距离；最低价大于aa的最低价；最低价大于60日均线}
    let H0_Ready = rawData.slice(i+1, i+4).every((item,index,arr) => {
        return item.close < rawData[i].high && item.low > rawData[i].low * 0.95
    })
    H0_Ready = H0_Ready && checkVOCS(rawData, i, AA_JL, extra)
    if (!H0_Ready) 
      return false
    let lastSA = getLastSA(rawData, i, extra)
    H0_Ready = H0_Ready && lastSA >= 0 && (i - lastSA > 0) && (i - lastSA < AA_JL)
    H0_Ready = H0_Ready && extra.AAs[i] && (rawData[i].low > rawData[extra.AAs[i]].low)
    H0_Ready = H0_Ready && (rawData[i].low > extra.close60[i])
    return H0_Ready
}


function checkVOCS(rawData, i, AA_JL, extra) {
  return extra.getCache('vocs', i + "_" + AA_JL, function(){
    if (AA_JL < 0) return false
    let counts = zytgTools.countDays(rawData, i, AA_JL, [
      function (raw, index, volume120) {
          //{定义vo：成交量大于120日均量的2倍}
          return raw[index].volume > volume120[index] * MHCL_VOLUME_INDEX
      }
    ], extra.volume120)
    //{定义vocs：在AAJL天数内满足VO的天数大于5}
    return counts > 5
  })
}

function getA_Ready(rawData, i, extra, AA_JL) {
  //定义AAH：低点AA以来的最高价
  let AAH = zytgTools.findMaxMinOfDays(rawData, i-1, AA_JL, zytgTools.MMD_GET_MAX, 'high')
  let VOCS = checkVOCS(rawData, i, AA_JL, extra)
  //{定义A：收盘大于昨日的AAH值，也就是收盘突破低点AA以来的最高价，并且满足条件vocs}
  return (rawData[i].close > AAH) && VOCS
}

function checkSA(rawData, i, extra) {
  let AA_JL = getAA_JL(extra.AAs, i)
  if (AA_JL < 0) return false

  let isA = false
  let hasAnotherA = false
  for (let j = 0; j < AA_JL; j++) {
      if (getA_Ready(rawData, i - j, extra, AA_JL - j)) {
        if (j == 0) {
          isA = true
        } else {
          hasAnotherA = true
        }
      }
      if (!isA || hasAnotherA)
        break
  }
  //{定义SA：满足条件A并且，上一个A的位置到现在的距离要大于低点AA到现在的距离 ==> 隐含逻辑：本A点是本次AA的首个突破点，即AA到上一天无其他A点}
  return isA && !hasAnotherA
}

export default {
  initParam: initParam,
  caculate: caculate
}
